package wsi.input;

import java.io.File;
import java.util.List;
import java.util.logging.Logger;

import org.rygh.core.util.UIo;
import org.rygh.core.util.UTime;
import org.rygh.core.xml.XmlLeaf;
import org.rygh.core.xml.XmlObject;
import org.rygh.core.xml.XmlTree;
import org.rygh.semantic.gui.swing.input.Input;
import org.rygh.semantic.jena.SDatabase;
import org.rygh.semantic.model.ex.FactList;
import org.rygh.semantic.model.ex.FactSet;
import org.rygh.semantic.model.ex.SampleWorker;
import org.rygh.semantic.model.rdf.Entity;
import org.rygh.semantic.model.rdf.Fact;
import org.rygh.semantic.model.rdf.Predicate;
import org.rygh.semantic.sparql.SparQL;

import wsi.detect.InputDetector;
import wsi.exp.Exp2TreeToEntity;
import wsi.exp.Exp4WebCallRatio;
import wsi.exp.ExpFolderController;
import wsi.function.DirManager;
import wsi.function.Function;
import wsi.function.RuntimeInfo;
import wsi.gui.tool.DBInjector;
import wsi.input.feedback.FeedbackTuple;
import wsi.script.AppConfig;

/**
 * <b>valid input:</b>
 * <ol>
 * <li>there is an stringset-style exact match to the input value and</li>
 * <li>the number of distinct matched values is larger than one or</li>
 * <li>for those xmls where the number of distinct matched values is equal to
 * one
 * <ul>
 * <li>the number of such xmls is larger than one and</li>
 * <li>the xmls with input values removed differ from each other.</li>
 * </ul>
 * </li>
 * </ol>
 * 
 * @author nanmus
 * 
 */
public abstract class InputSelector {
	protected final Logger log = Logger.getLogger(getClass().getName());

	public final static String CANDIDATE_INPUTS_XML = "candidate_inputs.xml";
	protected final static int TYPE_RECURSIVE_HOP = 5;

	protected final InputDetector detector;

	protected final Function function;
	protected final File inputFile;
	protected final DirManager dir;
	protected final Predicate namePredicate;
	protected final Predicate linkPredicate;

	protected final boolean isYago;
	protected final boolean loadPrevious;

	protected List<Input> candidatePool;
	protected Entity type;
	protected boolean offline = true;

	protected InputSelector(DirManager dir, boolean loadPrevious) {
		this.dir = dir;
		this.loadPrevious = loadPrevious;

		function = dir.getFunction();

		AppConfig ac = AppConfig.shared();

		isYago = ac.isYago();

		detector = new InputDetector(function);
		detector.setExpectedSize(ac.getInputSize());

		inputFile = new File(dir.getFunctionDir(), CANDIDATE_INPUTS_XML);

		if (function.getNamePredicate() == null)
			namePredicate = null;
		else
			namePredicate = new Predicate(function.getNamePredicate());

		Predicate pred = function.getRuntimeInfo().getLinkPredicate();

		if (pred == null)
			linkPredicate = null;
		else
			linkPredicate = new Predicate(pred);
	}

	protected abstract void compute();

	protected void detect(Input input) {
		detect(input, input.getEntity().getDegree(), null);
	}

	protected boolean detect(Input input, int degree, FeedbackTuple tuple) {
		return detector.detect(input, degree, tuple);
	}

	protected File getFile(boolean degree) {
		assert type != null;
		assert namePredicate != null;

		File dir = AppConfig.shared().getResourceInputFolder();

		String prefix_str = String.format("%s_%s", type.getBodyString(),
				namePredicate.getBodyString());
		String deg_str = "_degree";

		File file = null;

		for (File f : dir.listFiles()) {
			if (!f.isFile())
				continue;

			String name = UIo.getFileNameWithoutExtension(f.getName());

			if (!name.startsWith(prefix_str))
				continue;

			if (degree ^ name.endsWith(deg_str))
				continue;

			file = f;
			break;
		}

		return file;
	}

	public Function getFunction() {
		return function;
	}

	protected FactList getNamePredicateFacts() {
		FactList facts = new FactList();

		Entity vx, vy, vz;

		vx = new Entity("?x");
		vy = new Entity("?y");
		vz = new Entity("?z");

		if (linkPredicate == null)
			facts.add(new Fact(vx, namePredicate, vy));
		else {
			facts.add(new Fact(vz, linkPredicate, vx));
			facts.add(new Fact(vx, namePredicate, vy));
		}

		return facts;
	}

	protected Fact getTypeFact(Entity type) {
		final String pred = "w:type";

		if (linkPredicate == null)
			return new Fact("?x", pred, type.getValue());
		else
			return new Fact("?z", pred, type.getValue());
	}

	public Input[] load() {
		if (!detector.isInputEmpty()) {
			dir.setInputs(detector.getInputArray());
			return dir.getInputs();
		}

		dir.loadInputs();

		Input[] inputs = dir.getInputs();

		if (inputs != null)
			for (Input i : inputs)
				detector.addInput(i);

		return inputs;
	}

	protected boolean save() {
		if (detector.isInputEmpty()) {
			System.out.println("No input.");
			return false;
		}

		XmlTree xmltree = new XmlTree();
		XmlLeaf root = new XmlLeaf(xmltree.getRoot(), new XmlObject("inputs"));

		for (Input i : detector.getInputs())
			new XmlLeaf(root, i.toXmlObject());

		UIo.saveFile(inputFile, xmltree.toXmlString());

		return true;
	}

	private void saveExperiments() {
		Exp4WebCallRatio exp4 = new Exp4WebCallRatio();
		XmlTree tree = exp4.getXmlTree(detector.inputSize(),
				detector.getWebcalls(), detector.getTimeManager());

		UIo.saveFile(new File(ExpFolderController.shared().getFolderForExp4(),
				String.format("%s.xml", function.getFullName())), tree
				.toXmlString());
	}

	public final boolean select() {
		detector.clear();

		if (!function.isDirect()) {
			selectById(function.getRuntimeInfo());
		} else {
			if (inputFile.exists())
				return true;

			// timeManager.clear();

			if (loadPrevious)
				candidatePool = SimpleCandidatePool.shared().load(type,
						namePredicate);
			else
				candidatePool = null;

			if (candidatePool != null)
				System.out.printf("previous candidates size %d\n",
						candidatePool.size());

			compute();
			saveExperiments();
		}

		return save();
	}

	protected List<Input> select(SparQL query, boolean random) {
		int size = SampleWorker.SAMPLE_SIZE;

		if (!random) {
			DegreeCandidatePool pool = new DegreeCandidatePool(query, size);

			if (pool.load()) {
				System.out.printf("[info] query input hit %s, size %d.\n", pool
						.getFile().getName(), pool.getInputs().size());
				return pool.getInputs();
			}
		}

		SampleWorker sampler = new SampleWorker(null, query, namePredicate);
		sampler.setRandom(random);

		List<Input> inputs = sampler.compute();

		System.out.printf("[%s] sampled %d inputs.\n", UTime.toHumanString(),
				inputs.size());

		if (!random) {
			DegreeCandidatePool pool = new DegreeCandidatePool(query, size);
			pool.add(inputs);

			pool.save();
		}

		return inputs;
	}

	private boolean selectById(RuntimeInfo inputConfig) {
		Function prov_func = inputConfig.getProviderFunction();

		if (!Exp2TreeToEntity.isFunctionCorrectlyDetected(prov_func,
				inputConfig.getMatchProperties()))
			return false;

		Predicate idPred = inputConfig.getIdPredicate();
		FactSet facts = DBInjector.load(prov_func, idPred);

		System.out.printf("injecting %d facts (%s).\n", facts.size(),
				idPred.getValue());
		SDatabase.shared().insert(facts);

		for (Fact fact : facts) {
			Input input = new Input(fact.getSubject(), idPred, fact.getObject()
					.getValue());
			detector.addInput(input);
		}

		return true;
	}

	public void setOffline(boolean offline) {
		this.offline = offline;
	}
}
